/*
 * @(#)ResultGenerator.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.result;

import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.collections15.map.LRUMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.nhncorp.lucy.common.util.SpringObjectFactory;
import com.nhncorp.usf.core.UsfDispatcher;
import com.nhncorp.usf.core.config.AttributeInfo;
import com.nhncorp.usf.core.config.AttributeInfo.ATTRIBUTE_NAME;
import com.nhncorp.usf.core.config.ResultTypeInfo;
import com.nhncorp.usf.core.config.runtime.ConditionInfo;
import com.nhncorp.usf.core.config.runtime.ConditionInfoGroup;
import com.nhncorp.usf.core.config.runtime.ResultCondition;
import com.nhncorp.usf.core.config.runtime.ResultCondition.ResultConditionType;
import com.nhncorp.usf.core.config.runtime.ResultPageInfo;
import com.nhncorp.usf.core.config.runtime.ResultPageInfo.DISPATCHER_TYPE;
import com.nhncorp.usf.core.config.runtime.ResultPageInfos;
import com.nhncorp.usf.core.config.runtime.ScriptInfo;
import com.nhncorp.usf.core.servlet.ServletContextHolder;
import com.nhncorp.usf.core.util.OgnlUtil;
import com.nhncorp.usf.core.util.ScriptUtil;
import com.nhncorp.usf.core.xwork.UsfActionInvocation;
import com.opensymphony.xwork.Result;

/**
 * {@link ResultPageInfos}의 {@link DISPATCHER_TYPE} 에 따른 결과 이동 수행.
 *
 * @author Web Platform Development Team
 */
public class ResultGenerator {
    Log log = LogFactory.getLog(ResultGenerator.class);

    /**
     * 지원하는 {@link DISPATCHER_TYPE} 에 대한 {@link Result} {@link Map}
     */
    private Map<String, String> results;

    /**
     * {@link ResultPageInfo} 에 대한 {@link Result} 캐쉬.
     */
    private static final Map<ResultPageInfo, Result> RESULT_CACHE = Collections.synchronizedMap(new LRUMap<ResultPageInfo, Result>(
            65536));

    private static String checkDataModel;

    static {
        checkDataModel = AttributeInfo.getInstance().getAttribute(ATTRIBUTE_NAME.checkDataModel);
    }

    /**
     * 생성자.
     * 지원하는 {@link DISPATCHER_TYPE} 에 대한 {@link Result} 구현체를 등록한다.
     */
    public ResultGenerator() {
        results = new HashMap<String, String>();
        ResultTypeInfo resultTypeInfo = ResultTypeInfo.getInstance();

        for (Entry<String, String> entry : resultTypeInfo.getResultTypeInfos()) {
            results.put(entry.getKey(), entry.getValue());
        }
    }

    /**
     * {@link ResultPageInfos} 를 통한 결과 페이지 생성.
     *
     * @param globalResultPageInfos the ResultPageInfos
     * @param localResultPageInfos the ResultPageInfos
     * @param requestDataMap the request data
     * @return ResultPageInfo the result page information
     * @throws Exception the Exception 수행될 페이지에 대한 조건 검사 실패시 던져지는 예외.
     */
    public ResultPageInfo getResultPageInfo(ResultPageInfos globalResultPageInfos,
                                            ResultPageInfos localResultPageInfos, Map<String, Object> requestDataMap) throws Exception {

        ResultPageInfo resultPageInfo = null;
        ConditionInfo searchedConditionInfo = null;
        
        // check condition
        if (localResultPageInfos != null) {
        	searchedConditionInfo = getConditionInfo(localResultPageInfos.getConditions(), requestDataMap);
        }

        if (searchedConditionInfo == null && globalResultPageInfos != null) {
            searchedConditionInfo = getConditionInfo(globalResultPageInfos.getConditions(), requestDataMap);
        }

        if (searchedConditionInfo != null) {
            return searchedConditionInfo.getResultPageInfo();
        }

        // check default
        if (localResultPageInfos != null) {
            resultPageInfo = localResultPageInfos.getDefaultResultPageInfo();
        }
        
        if (resultPageInfo == null && globalResultPageInfos != null) {
            resultPageInfo = globalResultPageInfos.getDefaultResultPageInfo();
        }

        if (resultPageInfo == null) {
            throw new IllegalStateException("ResultPageInfo is null. "
                    + "Check page-result and result of action in runtime configuration");
        }

        return resultPageInfo;
    }

    /**
     * search {@link ConditionInfo} satisfied condition.
     *
     * @param conditionInfos 조건 검사할 {@link ResultCondition} 목록     * 
     * @param requestDataMap  the requst data map
     * @return the condition information
     * @throws Exception the Exception
     */
    private ConditionInfo getConditionInfo(List<ResultCondition> conditionInfos, Map<String, Object> requestDataMap) throws Exception {
        if (conditionInfos == null) {
            return null;
        }

        for (ResultCondition resultCondition : conditionInfos) {
        	if (ResultConditionType.condition == resultCondition.getResultType()) {
        		ConditionInfo conditionInfo = (ConditionInfo) resultCondition;
        		boolean isSatisfy = getCheckCondition(conditionInfo, requestDataMap);
        		
        		if (isSatisfy) {
                	if (conditionInfo.getResultPageInfo() != null) {
                		return conditionInfo;
                	} else {
                		return getConditionInfo(conditionInfo.getSubConditions(), requestDataMap);
                	}
                }
        		
        	} else if (ResultConditionType.conditionGroup == resultCondition.getResultType()) {
        		return getCheckCondition((ConditionInfoGroup) resultCondition, requestDataMap);
        	}
        	
        }

        return null;
    }

    /**
     * {@link ConditionInfo} 에 대해 condition 체크를 하고 true 일 경우 {@code resultCondtion} 을 반환한다. <br>
     * false 일 경우 null 을 반환한다.
     * 
     * @param resultConditionGroup 조건 검사할  {@link ConditionInfoGroup}
     * @param requestDataMap 사용자 입력 데이터
     * @return 조건을 통과한 {@link ConditionInfo}. 조건 통과한게 없으면 null
     * @throws Exception exception
     * */
    private ConditionInfo getCheckCondition(ConditionInfoGroup resultConditionGroup, Map<String, Object> requestDataMap) throws Exception {
    	return getConditionInfo(resultConditionGroup.getConditionInfos(), requestDataMap);
    }
    
    /**
     * {@link ConditionInfo} 에 대해 condition 체크를 하고 true 일 경우 {@code resultCondtion} 을 반환한다. <br>
     * false 일 경우 null 을 반환한다.
     * 
     * @param resultCondition 조건 검사할 {@link ConditionInfo}
     * @param requestDataMap 사용자 입력 데이터
     * @return Condition 이 조건을 만족하는지 여부
     * @throws Exception exception
     * */
    private boolean getCheckCondition(ConditionInfo resultCondition, Map<String, Object> requestDataMap) throws Exception {
    	ScriptInfo scriptInfo = resultCondition.getScriptInfo();
    	
        if (scriptInfo != null) {
            ScriptUtil.processControlScript(ServletContextHolder.getContextRealPath(), scriptInfo, requestDataMap);
        }
        try {
            Boolean isSatisfy = OgnlUtil.evaluateAndGet(resultCondition.getCodition(), requestDataMap);
            return isSatisfy.booleanValue();
        } catch (Exception e) {
            throw new ConditionEvaluateException(e);
        }
    }

	/**
     * {@link ResultPageInfo} 를 이용한 {@link Result} 생성/수행.
     *
     * @param usfActionInvocation the invaction instance
     * @throws Exception Exception the exception Result 실패시 던져지는 예외
     */
    public void executeResult(UsfActionInvocation usfActionInvocation) throws Exception {
        ResultPageInfo resultPageInfo = usfActionInvocation.getResultPageInfo();
        AbstractResult result;

        if (!UsfDispatcher.getInstance().isDevMode()) {
            result = getResult(resultPageInfo);
        } else {
            Map<String, Object> dataMap = usfActionInvocation.getDataMap();
            String modelType = (String)dataMap.get(checkDataModel);

            if (modelType != null && AttributeInfo.getInstance().containsKey(ATTRIBUTE_NAME.checkDataModel.toString())) {
                if (modelType.equalsIgnoreCase("xml")) {
                    result = getXmlDataModelResult(resultPageInfo);
                } else if (modelType.equalsIgnoreCase("json")) {
                    result = getJsonDataModelResult(resultPageInfo);
                } else {
                    result = getDataModelResult(resultPageInfo);
                }
            } else {
                result = getResult(resultPageInfo);
            }
        }

        // add requestParams to map
        Properties requestParams = result.getRequestParams();

        if (requestParams != null) {
            for (Entry<Object, Object> entry : requestParams.entrySet()) {
                usfActionInvocation.getDataMap().put((String)entry.getKey(), entry.getValue());
            }
        }

        result.execute(usfActionInvocation);
    }

    /**
     * Gets the data model result.
     *
     * @param resultPageInfo the result page info
     * @return the data model result
     * @throws IllegalAccessException    the illegal access exception
     * @throws InvocationTargetException the invocation target exception
     */
    private AbstractResult getDataModelResult(ResultPageInfo resultPageInfo) throws IllegalAccessException,
                                                                                    InvocationTargetException {
        // reflection 을 통해 Result 객체 생성 후 properties 와 requestParams 설정.
        AbstractResult result = SpringObjectFactory.getInstance().getObject(
                "com.nhncorp.usf.core.result.HTMLDataModelResult");
        BeanUtils.populate(result, resultPageInfo.getProperties());
        log.debug("constants : " + resultPageInfo.getRequestParams());
        result.setRequestParams(resultPageInfo.getRequestParams());

        log.debug("Result Object about 'HTMLDataModelResult' is created.");
        return result;
    }

    /**
     * Gets the result.
     *
     * @param resultPageInfo the result page info
     * @return the result
     * @throws IllegalAccessException    the illegal access exception
     * @throws InvocationTargetException the invocation target exception
     */
    private AbstractResult getResult(ResultPageInfo resultPageInfo) throws IllegalAccessException,
                                                                           InvocationTargetException {
        // ResultPageInfo 에 대한 Result 찾은 후 실행
        AbstractResult result = (AbstractResult)RESULT_CACHE.get(resultPageInfo);

        if (result == null) {
            // dispatcherType이 template or direct 이면 language 를 붙임.
            String resultType = resultPageInfo.getDispatcherType().name();

            if (DISPATCHER_TYPE.template.name().equalsIgnoreCase(resultType)) {
                String language = MapUtils.getString(resultPageInfo.getProperties(), "language", "freemarker");
                resultType = DISPATCHER_TYPE.template.name() + "." + language;
            } else if (DISPATCHER_TYPE.direct.name().equalsIgnoreCase(resultType)) {
                String language = MapUtils.getString(resultPageInfo.getProperties(), "language", "json");
                resultType = DISPATCHER_TYPE.direct.name() + "." + language;
            }

            // reflection 을 통해 Result 객체 생성 후 properties 와 requestParams 설정.
            result = SpringObjectFactory.getInstance().getObject(results.get(resultType));
            BeanUtils.populate(result, resultPageInfo.getProperties());
            log.debug("constants : " + resultPageInfo.getRequestParams());
            result.setRequestParams(resultPageInfo.getRequestParams());

            RESULT_CACHE.put(resultPageInfo, result);
            log.debug("Result Object about '" + resultType + "' is created.");
        }

        return result;
    }

    /**
     * Gets the xml data model result.
     *
     * @param resultPageInfo the result page info
     * @return the xml data model result
     * @throws IllegalAccessException    the illegal access exception
     * @throws InvocationTargetException the invocation target exception
     */
    private AbstractResult getXmlDataModelResult(ResultPageInfo resultPageInfo) throws IllegalAccessException,
                                                                                       InvocationTargetException {
        //
        // reflection 을 통해 Result 객체 생성 후 properties 와 requestParams 설정.
        AbstractResult result = SpringObjectFactory.getInstance().getObject(
                "com.nhncorp.usf.core.result.XMLDataModelResult");
        BeanUtils.populate(result, resultPageInfo.getProperties());
        log.debug("constants : " + resultPageInfo.getRequestParams());
        result.setRequestParams(resultPageInfo.getRequestParams());

        log.debug("Result Object about 'XMLDataModelResult' is created.");
        return result;
    }

    /**
     * Gets the json data model result.
     *
     * @param resultPageInfo the result page info
     * @return the json data model result
     * @throws IllegalAccessException    the illegal access exception
     * @throws InvocationTargetException the invocation target exception
     */
    private AbstractResult getJsonDataModelResult(ResultPageInfo resultPageInfo) throws IllegalAccessException,
                                                                                        InvocationTargetException {
        //
        // reflection 을 통해 Result 객체 생성 후 properties 와 requestParams 설정.
        AbstractResult result = SpringObjectFactory.getInstance().getObject(
                "com.nhncorp.usf.core.result.JSONDataModelResult");
        BeanUtils.populate(result, resultPageInfo.getProperties());
        log.debug("constants : " + resultPageInfo.getRequestParams());
        result.setRequestParams(resultPageInfo.getRequestParams());

        log.debug("Result Object about 'JSONDataModelResult' is created.");
        return result;
    }
}
